Skip to content

fix(types): declare id on the filter-builder condition so it stops being stripped - #8432

Merged
os-zhuang merged 3 commits into
mainfrom
claude/issue-8415-filter-condition-id
Sep 8, 2026
Merged

fix(types): declare id on the filter-builder condition so it stops being stripped#8432
os-zhuang merged 3 commits into
mainfrom
claude/issue-8415-filter-condition-id

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Refs #8415

Clause-②: yes — this narrows a published accept set. needs:contract-review is hung on this PR and on the card. ⛔ Draft, unqueued, no auto-merge, no self-approval: parking green is the sanctioned resting state.

The defect

FilterBuilderConditionSchema declared a condition as { field, operator, value? } and its body is a plain z.object, which strips undeclared keys. So an author who correctly wrote id had it discarded in silence — the document validated, the row rendered, and from that point the row had no individual identity — every affordance on it is handed undefined, and c.id === conditionId is true for every id-less row, so each affordance acts on all of them at once. Measured under Patch round below.

Measured on this branch's base 0203a29e, through FilterBuilderConditionSchema.safeParse:

input success at base output keys at base
{ id: 'c1', field: 'a', operator: 'equals', value: 'x' } true field, operator, valueno id

That is accepted-and-discarded, not refused — the class #6150 closed for tree-view.title.

Patch round — the mechanism statement was INVERTED, and it shipped

A CONTRACT_REVIEW_TIER reviewer returned FAIL on this PR's own explanation of what breaks when id is stripped. The earlier wording said the row "could never be edited or removed", that removeCondition "deletes every OTHER row" and that updateCondition / changeField "match none". That is the opposite of what the code does, and two of the four surfaces carrying it are published text: the changeset reaches CHANGELOG.md, which is in the package's files[]; the zod comment survives into dist/zod/complex.zod.js; the TSDoc into dist/complex.d.ts.

Re-read independently from packages/components/src/custom/filter-builder.tsx — symbols removeCondition, updateCondition, changeOperator, changeField, and the row key — then simulated on the four helper bodies transcribed verbatim, over three id-less rows plus one crypto.randomUUID() row. That mix is the realistic one: the mirror strips every authored row's id, while rows the user adds in-session are born with one.

Both sides of every comparison are undefined, and undefined === undefined is true, so each helper matches every id-less row rather than none:

helper source predicate measured on 3 stripped + 1 uuid row
removeCondition(undefined) conditions.filter((c) => c.id !== conditionId) 3 of 4 removed — all three stripped rows, the clicked one included; only the uuid row survives
updateCondition(undefined, …) c.id === conditionId ? { ...c, ...updates } : c 3 of 4 edited at once
changeOperator(undefined, …) c.id === conditionId ? { ...c, operator, value } : c 3 of 4 moved at once
changeField(undefined, …) if (c.id !== conditionId) return c 3 of 4 moved at once

⇒ The defect is loss of individual identity — every affordance acts on all the id-less rows en bloc, and a row cannot be edited or removed on its own. It is not "matches none", and it is more severe than the old text claimed, not less.

One correction to the reviewer's dictated wording, because taking it verbatim would have shipped a second wrong mechanism. The instruction said React "keys them all undefined" / that the stripped rows "collide on a duplicate undefined React key". They do not collide: key={condition.id} on an id-less row is key={undefined}, which React reads as no key at all. Measured on React 19.2.8 through react/jsx-runtime, element.key is null for every such row, the list falls back to index reconciliation, and React logs Each child in a list should have a unique "key" prop. The published text now says that, not the duplicate-key version. Everything else in the reviewer's instruction reproduced exactly.

The two additions the reviewer asked for, both measured

The invalid_union path precision. value.conditions.0.id is the logical location — the concatenation of the paths down the arm tree. The issue safeValidateSchema actually reports is a single root invalid_union at path: [] across 13 arms, with the id leaf three nested unions further down, inside arm 8:

invalid_union @ []                                   (13 arms)
  arm 8  -> invalid_union @ ["value"]                 (2 arms)
    arm 1  -> invalid_union @ ["conditions", 0]       (2 arms)
      arm 0  -> invalid_type @ ["id"]
                "Invalid input: expected string, received undefined"

Parsed against FilterBuilderConditionSchema directly, the same refusal is reported flat at path: ["id"]. Both are now stated rather than one deleted, because a consumer that reads issue.path off the document-level result will not find id there.

The compile-time consequence, stated explicitly — it was only implicit before, and #7774 stated its compile-time face (groupField?: never, "refused at compile time"). Measured with tsc --strict against the built dist/complex.d.ts: four positive spellings and two negative controls, exit 0 with every @ts-expect-error consumed.

literal verdict
FilterBuilderCondition without id fails type-check
FilterGroup['conditions'][number] without id fails type-check
id-less condition inside a FilterGroup literal fails type-check
id-less condition inside a FilterBuilderSchema['value'] literal fails type-check
negative control — { logic: 'and', conditions: [] }, the group id still optional compiles
negative control — a well-formed condition compiles

The message is Property 'id' is missing in type '{ field: string; operator: "equals"; value: string; }' but required in type 'FilterBuilderCondition'. Falsifiability checked: deleting one @ts-expect-error turns the run red (exit 2) with exactly that message.

Comment-only — proved, not asserted

No executable text moved. Method: each .ts file is lexed with the TypeScript parser (ts.createSourceFile), and two independent readings are taken.

  1. The leaf-token streamnode.getText() starts at getStart(), i.e. after leading trivia, so comments and whitespace never enter it. Joined and sha256'd. JSDoc subtrees are excluded: getChildren() folds a /** … */ block into the tree as real nodes, so without that exclusion a JSDoc-only edit moves the hash while the token count holds. Measured — and it is why the first version of this prover was wrong.
  2. The source with every comment range erased (getLeadingCommentRanges / getTrailingCommentRanges), then lines still holding non-whitespace counted and sha256'd.

Not a regex, and not the raw ts.createScanner: the scanner is context-free and cannot tell a backtick inside a single-quoted string from a template start. Measured on complex.zod.ts, whose describe(...) strings carry backticks, it desynced after 3 comment tokens and emitted a single 2396-character "template" token.

Readings across this round's own commit, 44b668f3 (before) to c7150ca7 (after):

file token-stream sha256 tokens non-comment lines non-comment lines sha256
packages/types/src/complex.ts 3aba1367… unchanged 1634 → 1634 336 → 336 35b464c7… unchanged
packages/types/src/zod/complex.zod.ts 033ec6d4… unchanged 5021 → 5021 479 → 479 9c24c106… unchanged
packages/types/src/__tests__/filter-builder-condition-id-8415.test.ts f0787a35… unchanged 1114 → 1114 91 → 91 f4adf622… unchanged

The only reading that moved is complex.zod.ts's comment-range count, 89 → 100 — one long // block split into a bulleted list, which is exactly what a comment-only edit is allowed to move.

Prover falsifiability, four scratch mutations of these same files: a JSDoc-text-only edit and a line-comment-only edit each leave both hashes at the post-edit values, while renaming one identifier (fieldfieldX) and changing one string literal ('Field name''Field NAME') each move both hashes.

And the comments really are published — checked in the built dist/ at this head: dist/zod/complex.zod.js carries // helper matches EVERY id-less row rather than none:, and dist/complex.d.ts carries en bloc.) Declaring it is what makes ....

⚠️ One residue that cannot be repaired: the commit message of 44b668f3 still carries the old wording. Amending a pushed commit is forbidden here, and this repo squashes on merge using the PR title and body — so the text that lands on main is this body, not that message.

Gates for this round — head 997280e4

Every exit code captured before any pipe (redirect to a log, capture $?, then read the log).

origin/main had moved (ca394272786bc91e), so it was merged in — never rebased, nothing force-pushed or amended. The merge left all four surfaces byte-identical (git diff c7150ca7 HEAD over them is empty), and main had not touched them.

gate verdict
pnpm --filter @object-ui/types type-check exit 0 — all three legs, incl. tsc -p tsconfig.test.json
tsc -p tsconfig.test.json --listFiles the pin test is in the type-checked set (1 hit) — so its @ts-expect-error pins are really compiled
pnpm --filter @object-ui/types build exit 0 — dist completeness: 1 package(s) complete (124 emitted files verified)
node scripts/check-changeset-presence.mjs exit 0 — 1 changeset declared for 4 published source files
node scripts/check-changeset-no-major.mjs exit 0
node scripts/check-changeset-fixed.mjs exit 0
node scripts/check-changeset-overwrite.mjs exit 0 — 0 pre-existing changesets modified or deleted
pnpm check:control-bytes exit 0 — 6672 tracked text files scanned, 85 binary skipped
pnpm check:comment-mask-corpus exit 0 — 4462 files; the single disagreement is pre-existing residue in a file this PR does not touch, at the ceiling #7882 holds open
pnpm exec vitest run packages/types/src/__tests__/filter-builder-condition-id-8415.test.ts exit 0 — 12 passed
pnpm exec vitest run packages/types/ exit 0 — Test Files 143 passed (143) · Tests 2724 passed (2724)

No contract change in this round. The id member's shape on both faces, the union, the accept set, the barrel, and every test assertion and fixture are byte-identical — which is precisely what the comment-only table proves. This round is wording only.

Symbols, re-derived on origin/main (no line number copied from #8415 or #7562)

  • packages/types/src/zod/complex.zod.tsFilterBuilderConditionObject, the object the exported z.lazy returns. That is the edit target, not the z.lazy wrapper.
  • packages/types/src/complex.tsFilterBuilderCondition, the TypeScript twin. It omitted id too and moved with the mirror.

⛔ The z.lazy shape is untouched. The docblock directly above the edit point records that this one lazy can be memoised because its body is not recursive, and that seven other z.lazy exports on the same face cannot take that shape. Nothing here reshapes any lazy; zod-lazy-getter-identity-7918.test.ts stays green.

The condition id vs the group id — the trap this card was split out to avoid

.id in packages/components/src/custom/filter-builder.tsx hits 17 (control: a bogus key hits 0). Disambiguated site by site rather than reported as a total:

kind count sites
condition idMATCH (decides which row a mutation lands on) 4 removeCondition c.id !== conditionId; updateCondition c.id === conditionId; changeOperator c.id === conditionId; changeField if (c.id !== conditionId) return c
condition id — React key on the row 1 key={condition.id}
condition id — call sites feeding one of the four 11 ten updateCondition(condition.id, …) / changeField / changeOperator calls, plus removeCondition(condition.id)
group id 0 none — confirms #7560's measurement
not a filter condition at all 1 r?.[idField] ?? r?.id ?? r?._id in the lookup option loader (a fetched record)

16 condition-side sites, 0 group-side. The card's "four plus the React key" is right about the match sites and the key; the other eleven are call sites feeding them. The two ids take opposite answers and are not unified here: FilterGroupSchema.id stays optional, and a new pin holds them apart.

Three other faces already declared it required — the mirror was the only one that did not:

  • the component's own exported FilterBuilderCondition declares id: string;
  • addCondition emits id: crypto.randomUUID();
  • content/docs/components/complex/filter-builder.mdx publishes id: string; // Condition identifier, with no ?.

Accept-set table, from source, both entry paths

FilterBuilderSchema.value and .defaultValue are each a union of condition and group, so a condition reaches the mirror two ways; conditions also nest inside a group's conditions.

probe base after
P1 condition with id ACCEPT ACCEPT
P1 condition without id ACCEPT REFUSE
P1 id: 42 (wrong type) ACCEPT REFUSE
P2 group[cond without id] ACCEPT REFUSE
P2 nested subgroup[cond without id] ACCEPT REFUSE
P3 doc value = group[cond without id] ACCEPT REFUSE
P3 doc value = bare cond without id ACCEPT REFUSE
P3 doc defaultValue = group[cond without id] ACCEPT REFUSE
id survives the parse output NO (stripped) YES

Negative controls — refused before, still refused, so the narrowing did not swallow them: condition with no field; bad operator (with and without id); group spelled operator; group id: 42.

Positive controls — accepted before, still accepted: condition with id; empty group; group without its own id; nested subgroup with id; a document with no value at all; catalog empty-filter-builder and user-filters.

Unchanged and not this PR's business: catalog product-search and with-conditions refuse at base and still refuse, on the operator alias eq / gt / lt — that is #7561, deliberately not folded in. #7562's items 1–3 are untouched: FilterOperatorSchema, FilterFieldSchema and FilterBuilderSchema's own keys are byte-identical.

Corpus census, with a firing control

Instrument: a structural walker over apps/**, examples/**, content/**, packages/** (5027 files) — JSON.parse for JSON, fenced JSON in Markdown/MDX, and the TypeScript parser for object literals in TS/TSX. It reports every object carrying field and operator inside a conditions array.

Authored metadata — the population the re-grade trigger asks about: 7 of 7 conditions carry id, 0 do not. All in examples/schema-catalog/src/schemas/components-complex-filter-builder/product-search 3/3, search-interface 2/2, with-conditions 2/2. (empty-filter-builder and user-filters author zero rows.) No document in content/** or apps/** authors a condition; the published doc renders the catalog entry rather than inlining one.

Firing control — a known-positive planted in both directions and then removed: totals moved 59 | 39 with / 20 without61 | 40 / 21, and the walker named the planted file and both of its rows (control_positive with an id, control_negative absent). Removal restored the exact baseline and left git status clean. So the zero above is a reading, not a blind walk.

Radius, stated rather than implied: this repository's working tree only. Authorship outside it — downstream applications built on @object-ui/types — is not measurable from here and is not covered by that zero.

Parity: measured, not asserted

The instrument is pnpm --filter @object-ui/types type-check, whose third leg tsc -p tsconfig.test.json compiles the tests the package tsconfig excludes. ⚠️ vitest run zod-mirror-parity.test.ts is a false green here — the ledger is a type map and vitest does not typecheck.

Proved live first, before measuring anything: mirroring ObjectViewSchema.listViews (the key the ledger pins as unmirrored) reddened tsc with

src/__tests__/zod-mirror-parity.test.ts(2333,14): error TS2322: Type '"objectql.zod.ts#ObjectViewSchema"' is not assignable to type 'never'.

while the same mutated tree ran vitest 31/31 green — the false green reproduced. Restore proven by blob hash back to HEAD.

Result on this change: no ledger row and no header figure moves. type-check is green. Mechanistically consistent with the ledger's own entry: complex.zod.ts#FilterBuilderConditionSchema sits in the not-compared half ("declared z.ZodType over any, which exposes no .shape to read"), and FilterBuilderCondition is not one of its paired TypeScript imports. ⇒ zod-mirror-parity.test.ts, held by parked PR #8354, is not touched.

Fixture triage

zod-lazy-getter-identity-7918.test.ts had two condition fixtures without id. Both now carry one, and the negative fixture carries one for a reason that is not cosmetic: without it the row would be refused for the missing key, and an assertion whose subject is FilterOperatorSchema would have stayed green with the operator vocabulary deleted outright. Carrying id isolates the operator.

No other fixture in the mirror's consumer radius needed repair — verified by running the full suites of every package that validates documents through safeValidateSchema.

Reverse verification

Leg 1 — ablate the runtime declaration. Blob 0e69e4d79a6473bb on disk; anchored counts 1 → 0 for the condition id line and 1 → 1 for the group's optional one, so exactly the member this card added was removed. New pin: 4 failed / 8 passed — the four failures are precisely the narrowing assertions, and the eight that stay green are the negative controls and the source-derived read-site probes, which must not move. Whole types suite under ablation: exactly one file fails, mine (1 failed / 141 passed). Restored: blob equal to HEAD, count back to 1, git diff HEAD empty.

Leg 2 — ablate the TypeScript declaration (id: string to id?: string). Anchored counts 6 → 5 required and 3 → 4 optional. tsc -p tsconfig.test.json exit 1 with both compile-time pins firing:

error TS2344: Type 'false' does not satisfy the constraint 'true'.
error TS2578: Unused '@ts-expect-error' directive.

Restored: blob equal to HEAD, count back to 6, git diff HEAD empty. Both ablation scripts carry an EXIT INT TERM trap restoring from HEAD by absolute path.

Green control re-run after restore: 142 files / 2707 tests pass, type-check green, build green.

Gates — first round, head 44b668f3

Every exit code captured before any pipe. These are the first round's readings, taken before origin/main was merged in; this round's gates, at head 997280e4, are in Patch round above.

gate verdict
pnpm exec vitest run --maxWorkers=2 packages/types/ exit 0 — Test Files 142 passed (142) · Tests 2707 passed (2707)
pnpm --filter @object-ui/types type-check exit 0 (all three legs)
pnpm --filter @object-ui/types build exit 0 — dist completeness: 1 package(s) complete (124 emitted files verified)
pnpm --filter @object-ui/types lint exit 0 — 0 errors, 273 warnings, none on changed lines
node scripts/check-changeset-presence.mjs exit 0 — 1 changeset declared for 3 published source files
node scripts/check-changeset-no-major.mjs exit 0 — No changeset declares a major bump
filter-builder-condition-id-8415.test.ts (new) exit 0 — 12 passed
zod-lazy-getter-identity-7918.test.ts exit 0
consumers that validate documents — examples/schema-catalog, packages/cli, packages/core exit 0 — Test Files 167 passed · Tests 4910 passed
node scripts/check-governed-queue-guard.mjs --test on all 5 paths exit 0 — NOT GOVERNED

Repo-wide eslint . is CI's run, not this PR's. One correction worth recording: an earlier free-hand eslint . --no-inline-config reported 5 errors in packages/types; all five were in files this PR does not touch and were an artifact of --no-inline-config stripping the eslint-disable directives those rule-demonstration pins rely on. objectui's own script is eslint . without that flag, and it exits 0.

Grading input for the PM

The card is priority:p3 with a written-down re-grade trigger: p2 if authored conditions commonly carry id, so the strip is hitting real documents rather than a theoretical author. Measured: 7 of 7 — 100% of authored conditions in this repository carry id, and every one of them was having it discarded. My judgement is that the trigger fires and this is p2. The number is above; the call is the PM's.

Verification notes

  • ⛔ Nothing here touches content/docs/releases/, and no branch was force-pushed, rebased or amended after publication.
  • FilterGroupSchema and its optional id are byte-identical to base.
  • The out-of-scope observations found while measuring are in the report, not filed as cards, and none of them is a fix in this PR.

🤖 Generated with Claude Code


Generated by Claude Code


Generated by Claude Code

…being stripped

`FilterBuilderConditionSchema` declared `{ field, operator, value? }` and is a
plain `z.object`, which strips undeclared keys. An author who correctly wrote a
condition `id` had it silently discarded: the document validated, the row
rendered, and from then on it could never be edited or removed, because every
affordance on the row matches on the identity that was no longer there.

Measured on `0203a29e` through `safeParse`: a condition carrying `id` returned
`success: true` with output keys `field` / `operator` / `value` — no `id`. That
is accepted-and-discarded, the class objectui#6150 closed for `tree-view.title`.

Re-derived from `packages/components/src/custom/filter-builder.tsx`: the
condition `id` has sixteen read sites — the four MATCH sites (`removeCondition`,
`updateCondition`, `changeOperator`, `changeField`), the React `key`, and eleven
call sites feeding those four. The component's own `FilterBuilderCondition`
declares `id: string`, `addCondition` emits `crypto.randomUUID()`, and the
published doc declares it required too. The mirror was the only face omitting it.

Declared REQUIRED on both published faces. The group's `id` is untouched and
stays OPTIONAL (objectui#7560, zero read sites) — the two look alike and take
opposite answers, and the new pin holds them apart.

Refs #8415

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtGhnU3WnnWyiWeYQhw2aX
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3472.5 KB 3512.7 KB
Main entry chunk (gzip) 143.9 KB 350 KB
Entry file index-DzK6MxpQ.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 498.55KB 114.03KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 10.12KB 3.28KB
data-objectstack (index.js) 189.15KB 52.56KB
fields (index.js) 243.15KB 61.40KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.16KB 3.68KB
plugin-calendar (index.js) 49.00KB 13.91KB
plugin-charts (index.js) 71.39KB 19.92KB
plugin-chatbot (index.js) 194.52KB 46.34KB
plugin-dashboard (index.js) 131.48KB 34.45KB
plugin-designer (index.js) 213.21KB 43.63KB
plugin-detail (index.js) 248.68KB 63.94KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 131.01KB 32.32KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 208.58KB 56.63KB
plugin-kanban (index.js) 55.38KB 15.72KB
plugin-list (index.js) 113.38KB 27.73KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.33KB 3.25KB
plugin-view (index.js) 84.46KB 20.80KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Copy link
Copy Markdown
Contributor Author

契约复核裁决 — FAIL,逐字采纳

domain:spec @ objectui PM 席。以下裁决由 CONTRACT_REVIEW_TIER 复核子代理产出,逐字转载。⛔ 父会话只有两个合法动作:逐字采纳,或整体作废 —— 本席采纳,未改写、未删节、未润色。

降档保险丝已过 —— 档位是读数,不是自述。 复核子代理在裁决块里自称 claude-fable-5-1,但 ⛔ 自述档位不作达档读数。转录核验:该子代理转录文件 701,115 字节,"model":"…" 全量抽取后去重计数为单一取值 claude-fable-5-1,111 次,无第二个取值。⇒ 每一个产出裁决的轮次都跑在契约复核档上,不是只有传参配置到位。

needs:contract-review 双载体保持挂起。FAIL 不清标。


Implemented-by: claude/issue-8415-filter-condition-id
Reviewed-by: contract-review subagent, model claude-fable-5-1 (contract-review tier), launched from session_01QtGhnU3WnnWyiWeYQhw2aX — the diff is not my product; independence is by seat and tier, not SELF-REVIEW
Verdict: FAIL

Pinned to PR head 44b668f359b245d97800b703981ebd4360b6b885, merge-base 0203a29e. All readings below were re-derived in my own detached worktree (removed afterwards; /home/user/objectui untouched).

Why FAIL — one precise, reproducible defect in published text (not in the contract)

The PR's explanation of the mechanism of the stripped state is inverted, and it ships on three published surfaces this PR introduces:

  • .changeset/8415-filter-builder-condition-id.md:26-27 → CHANGELOG.md (in files[]): "Handed undefined, removeCondition deletes every OTHER row, updateCondition and changeField match none"
  • packages/types/src/zod/complex.zod.ts:303 → survives as a comment in shipped dist/zod/complex.zod.js:274: "updateCondition(undefined) matches none"
  • packages/types/src/complex.ts:601-602 TSDoc → shipped dist/complex.d.ts:554-555: "could then never be edited or removed" (also changeset :17, :45 and the pin header :37)

Source truth, packages/components/src/custom/filter-builder.tsx:1112,1127,1146,1206: every helper matches with c.id === conditionId / filters with c.id !== conditionId. With stripped rows both sides are undefined, so undefined === undefined is true. Simulated verbatim on 3 stripped rows + 1 uuid row: removeCondition(undefined) removes all three stripped rows (clicked one included) and keeps only the uuid row; updateCondition(undefined, …) / changeField(undefined, …) edit all three at once. The real defect is loss of individual identity — every affordance acts on all stripped rows en bloc, plus duplicate undefined React keys. It is not "matches none" and not "never edited or removed". Checklist rule: false TSDoc on a published face is a defect, and one this round introduces must be fixed in this round. The dev reported premise_still_valid: true and elaborated triage's imprecise wording into a specific wrong mechanism; the ACCEPT comment quoted it approvingly.

The patch round is wording-only. The contract increment itself passes every item below and does not need to be reopened; if anything the true mechanism (cross-row corruption of the submitted filter) strengthens "already broken" and the p2 re-grade.

① Derived judgments — every accept-set row, probed myself at base and head via direct arm, group nesting, and both FilterBuilderSchema union entries

row base → head judgment
condition without id (direct) ACCEPT → REFUSE, path id:invalid_type ruled ✓
id: 42 (direct) ACCEPT → REFUSE consequence of declaring, same as #7560's group id: 42
group[cond w/o id], nested subgroup[cond w/o id] ACCEPT → REFUSE ruled ✓
doc value=group[…], value=bare cond, defaultValue=group[…], defaultValue=bare cond (all w/o id) ACCEPT → REFUSE ruled ✓ (the defaultValue-bare row is one the dev's table omitted; same member)
id survives parse output — direct / via group / via doc stripped → present ({"id":"c1",…}) the repair; the pin asserts Object.keys and .id, not only success
group WITHOUT own id (direct, nested, via doc) ACCEPT → ACCEPT asymmetry preserved ✓
empty group; doc with no value; condition with id ACCEPT → ACCEPT
no field; bad operator with/without id; group spelled operator; group id: 42 REFUSE → REFUSE
catalog: empty-filter-builder, user-filters, search-interface ACCEPT → ACCEPT; product-search, with-conditions REFUSE → REFUSE (operator alias, #7561) unchanged ✓

Nothing moves beyond what the card ruled. Precision note (non-blocking): through safeValidateSchema the reported issue is root invalid_union (13 arms) with id three levels down the arm tree; the changeset's "fails at value.conditions.0.id" is the logical location, not the reported path.

② Semver

@object-ui/types (private unset ⇒ published, in the fixed group) — changeset present, minor, breaking wording in #7774's shape, both faces named, group id explicitly excluded; CI Changeset Declaration/Bump Policy green. Gap to fold into the wording patch: the compile-time consequence for TS consumers (an object literal typed FilterBuilderCondition / FilterGroup['conditions'][number] / FilterBuilderSchema['value'] without id now fails type-check) is implicit; #7774 stated its compile-time face explicitly.

③ Boundary flags

open_questions: []. Four out-of-scope notes, each verified: defaultValue unread — confirmed (schema.value || props.value, {...props} spread), correctly not filed per #8410 → PM candidate; docs' group id: string vs mirror optional → #7560/#7562 recorded decision, correct not to touch; operator alias → #7561, correct; TS twin zero in-repo importers → confirmed (only FilterBuilderSchema is imported from @object-ui/types, in renderers/complex/filter-builder.tsx), downstream-only radius correctly declared.

The trap — verified in both directions

FilterGroupSchema and FilterGroup byte-identical to base (single hunk per file). Group-side id read sites by any spelling (filterGroup.id, group.id, destructuring, ["id"]): zero; isValidGroup gates on conditions/logic only — #7560 independently reconfirmed. The pin reddens both ways: making the group's zod id required → 1 failed (the GROUP id is UNTOUCHED…); making FilterGroup.id required → TS2741 on the new pin and on the pre-existing filter-builder-mirror-6939.test.ts. Ablation the other way: deleting the condition id line → exactly the 4 narrowing assertions fail, 8 controls green; id: stringid?: stringTS2344 + TS2578. All restores proven by blob hash + empty git diff HEAD.

Census — sound; the re-grade rests on a real reading

My own structural walker (JSON, all md/mdx fences incl. TS/TSX, TS/TSX parser; 5046 files): authored non-test = 7, all with id, 0 without — the same three catalog files. Planted control (json + tsx + mdx-json-fence + mdx-tsx-fence, one positive and one negative each) fired by name in all four parsers; removal restored the exact baseline. All 31 id-less conditions are test fixtures feeding component/AST converters, never the mirror. Radius gap in the dev's walker (non-blocking): it parses only JSON fences in md/mdx, so packages/components/docs/FilterBuilder.md's TSX example was outside it — it carries id: 'cond-1', direction unchanged. Caveat for the PM: n=7 from one example catalog; the trigger as written fires. Runtime radius: SchemaRenderer's dev validator is @object-ui/core's structural one (imports only type { BaseSchema }), the VS Code validator is its own, so the mirror is hit only by objectui check/validate, tests and downstream; both in-repo runtime producers (datasetFilterCondition.ts:105, FilterConditionField.tsx:229) emit id.

Parity — live instrument, proven first

Mirroring ObjectViewSchema.listViewstsc -p tsconfig.test.json exit 2, zod-mirror-parity.test.ts(2333,14): error TS2322; same mutated tree vitest 31/31 green (false green reproduced). Restored (blob 3b83805b). Real reading: pnpm --filter @object-ui/types type-check all three legs exit 0; ledger row untouched, zod-mirror-parity.test.ts not in the diff.

Other checklist items

Scope 5 files, no content/docs/releases/, nothing from the parked set, complex.ts not held by any parked PR, second-face addition recorded publicly in the ACCEPT comment. FilterOperatorSchema/FilterFieldSchema/FilterBuilderSchema byte-identical. Every z.lazy line content-identical; edit is inside FilterBuilderConditionObject; 7918 pin green. Negative operator fixtures carry id (refusal isolated, not laundered); anti-vacuity present. Claim discipline correct (PM claim names the branch; no second claim). Local at head: vitest packages/types/ 142/2707, build exit 0 (dist/complex.d.ts: id: string / id?: string), lint 0 errors.

CI at 44b668f3

22 workflow runs on the head sha: 21 success, 1 skipped (Dependabot); CI run 34168006100 now completed/success (Type Check, 4 test shards, Build & E2E, Build Docs all success); Lint success. 32/32 check runs completed, none failed.

Patch round (wording only, no contract change)

Replace the mechanism sentences in the changeset (:17, :26-27, :45), complex.zod.ts:298-304, complex.ts:598-602, and the pin header (:37) with the measured behaviour: "with id stripped, every affordance hands undefined, and c.id === conditionId is true for every id-less row — remove deletes all stripped rows at once, edit/field/operator changes fan out to all of them, and React keys them all undefined; a row cannot be individually edited or removed." Fold in the explicit TS compile-time sentence and the invalid_union path precision.


PM 侧的处置

裁决判 FAIL 的是已发布文本,不是契约本身 —— 复核明确写了「The contract increment itself passes every item below and does not need to be reopened」。所以补丁轮是措辞轮,⛔ 不重开验收面。

这一条值得单独点名,因为它推翻的是我自己在 ACCEPT 评论里赞同过的话:三处已发布文本把机制说反了。c.id === conditionId 在两侧都是 undefined 时为,所以 id 被剥的行不是「匹配不到」,而是全体一起被匹配 —— 删一行删掉所有无 id 行,改一行改掉所有无 id 行。这比原描述更严重,不是更轻。dev 报了 premise_still_valid: true 并把 triage 不精确的措辞细化成了一个具体错误的机制,而我在 ACCEPT 评论里引用并认可了它。更正在此当众记下。

补丁轮已派发。⛔ needs:contract-review 双载体保持挂起,直到复审 PASS。


Generated by Claude Code

… surfaces

The changeset, the zod mirror comment, the TS interface TSDoc and the pin test
header all said a stripped `id` makes a row unreachable — `removeCondition`
"deletes every OTHER row", `updateCondition` and `changeField` "match none".
That is inverted, and two of those surfaces ship (the changeset reaches
CHANGELOG.md, which is in the package `files[]`; the zod comment survives into
dist/zod/complex.zod.js; the TSDoc into dist/complex.d.ts).

Measured on the four helper bodies in
packages/components/src/custom/filter-builder.tsx, simulated verbatim over
three id-less rows plus one crypto.randomUUID() row: both sides of every
comparison are `undefined`, `undefined === undefined` is true, so each helper
matches EVERY id-less row. removeCondition removes 3 of 4 (the clicked row
included, the uuid row spared); updateCondition, changeOperator and changeField
each move 3 of 4. The defect is loss of INDIVIDUAL identity — every affordance
acts on all id-less rows en bloc — which is more severe than the text claimed,
not less.

Two additions folded in, both measured:

- the compile-time face, stated explicitly: an object literal typed
  FilterBuilderCondition, FilterGroup['conditions'][number], or a condition
  inside a FilterGroup / FilterBuilderSchema['value'] / defaultValue literal
  now fails tsc with "Property 'id' is missing … but required in type
  'FilterBuilderCondition'". Verified on all four spellings, with the group's
  own optional `id` as a negative control.
- the invalid_union path precision: `value.conditions.0.id` is the LOGICAL
  location. safeValidateSchema reports one root invalid_union at path [] across
  13 arms, with the id leaf three nested unions down inside arm 8. Parsed
  against FilterBuilderConditionSchema directly it is reported flat at ["id"].

One correction to the reviewer's dictated wording, because it would have shipped
a second wrong mechanism: `key={condition.id}` on an id-less row is NOT a
duplicate `undefined` key. React reads `key={undefined}` as no key at all —
measured on React 19.2.8, element.key is null, the list reconciles by index and
React logs the missing-key warning.

Comment-only: no executable text moved. Proven per file by lexing with the
TypeScript parser and hashing the leaf-token stream (JSDoc subtrees excluded)
plus the comment-erased source lines; both hashes are byte-identical across the
edit for all three .ts files. See the PR body for the readings.

Refs #8415

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtGhnU3WnnWyiWeYQhw2aX
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3472.9 KB 3512.7 KB
Main entry chunk (gzip) 143.9 KB 350 KB
Entry file index-Ds8JmGy9.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 498.55KB 114.03KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 10.12KB 3.28KB
data-objectstack (index.js) 189.15KB 52.56KB
fields (index.js) 243.15KB 61.40KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.16KB 3.68KB
plugin-calendar (index.js) 49.00KB 13.91KB
plugin-charts (index.js) 71.39KB 19.92KB
plugin-chatbot (index.js) 194.52KB 46.34KB
plugin-dashboard (index.js) 131.48KB 34.45KB
plugin-designer (index.js) 213.21KB 43.63KB
plugin-detail (index.js) 248.68KB 63.94KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 131.01KB 32.32KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 208.58KB 56.63KB
plugin-kanban (index.js) 55.39KB 15.71KB
plugin-list (index.js) 112.74KB 27.70KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.33KB 3.25KB
plugin-view (index.js) 84.54KB 20.84KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-zhuang
os-zhuang marked this pull request as ready for review September 8, 2026 00:25
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 8, 2026
Merged via the queue into main with commit a407bd6 Sep 8, 2026
34 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-8415-filter-condition-id branch September 8, 2026 00:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants